{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# Get started\n",
    "LCEL makes it easy to build complex chains from basic components, and supports out of the box functionality such as streaming, parallelism, and logging.<br>\n",
    "LCEL 可以轻松地从基本组件构建复杂的链，并支持开箱即用的功能，例如流式处理、并行性和日志记录。\n",
    "\n",
    "LangChain Expression Language is a way to create arbitrary custom chains. It is built on the Runnable protocol.<br>\n",
    "LangChain表达式语言是一种创建任意自定义链的方法。它建立在 Runnable 协议之上。"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Basic example: prompt + model + output parser\n",
    "\n",
    "> The | symbol is similar to a unix pipe operator, which chains together the different components, feeding the output from one component as input into the next component. <br>\n",
    "该 | 符号类似于 unix 管道运算符，它将不同的组件链接在一起，将一个组件的输出作为输入馈送到下一个组件。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 1,
   "metadata": {},
   "outputs": [],
   "source": [
    "from langchain_openai import ChatOpenAI\n",
    "\n",
    "model = ChatOpenAI(model=\"gpt-4o-mini\")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "'Why did the ice cream cone become a singer? \\n\\nBecause it had a great \"scoop\" of talent! 🍦🎤'"
      ]
     },
     "execution_count": 2,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "from langchain_core.output_parsers import StrOutputParser\n",
    "from langchain_core.prompts import ChatPromptTemplate\n",
    "\n",
    "prompt = ChatPromptTemplate.from_template(\"tell me a short joke about {topic}\")\n",
    "output_parser = StrOutputParser()\n",
    "\n",
    "chain = prompt | model | output_parser\n",
    "\n",
    "chain.invoke({\"topic\": \"ice cream\"})"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "> In this chain the user input is passed to the prompt template, then the prompt template output is passed to the model, then the model output is passed to the output parser. Let's take a look at each component individually to really understand what's going on.<br>\n",
    "在此链中，用户输入被传递到提示模板，然后提示模板输出被传递给模型，然后模型输出被传递到输出解析器。让我们分别看一下每个组件，以真正了解发生了什么。"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 1. Prompt\n",
    "prompt is a BasePromptTemplate, which means it takes in a dictionary of template variables and produces a PromptValue.<br> \n",
    "A PromptValue is a wrapper around a completed prompt that can be passed to either an LLM (which takes a string as input) or ChatModel (which takes a sequence of messages as input). It can work with either language model type because it defines logic both for producing BaseMessages and for producing a string.<br>\n",
    "prompt 是BasePromptTemplate 这意味着它接受模板变量的字典并生成 PromptValue .<br>\n",
    "A PromptValue 是围绕已完成提示的包装器，可以传递给 an LLM （将字符串作为输入）或 ChatModel （将一系列消息作为输入）。它可以与任何一种语言模型类型一起使用，因为它定义了用于生成 BaseMessage s 和生成字符串的逻辑。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 3,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "ChatPromptValue(messages=[HumanMessage(content='tell me a short joke about ice cream')])"
      ]
     },
     "execution_count": 3,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "prompt_value = prompt.invoke({\"topic\": \"ice cream\"})\n",
    "prompt_value"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 4,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "[HumanMessage(content='tell me a short joke about ice cream')]"
      ]
     },
     "execution_count": 4,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "prompt_value.to_messages()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 5,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "'Human: tell me a short joke about ice cream'"
      ]
     },
     "execution_count": 5,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "prompt_value.to_string()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 2. Model\n",
    "The PromptValue is then passed to model. In this case our model is a ChatModel, meaning it will output a BaseMessage.<br>\n",
    "PromptValue 然后传递给 model 。在本例中，我们 model 是一个 ChatModel ，这意味着它将输出一个 BaseMessage 。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 6,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "AIMessage(content='Why did the ice cream cone break up with the sundae? \\n\\nBecause it found someone cooler!', response_metadata={'token_usage': {'completion_tokens': 20, 'prompt_tokens': 15, 'total_tokens': 35}, 'model_name': 'gpt-4o-mini-2024-07-18', 'system_fingerprint': 'fp_8b761cb050', 'finish_reason': 'stop', 'logprobs': None}, id='run-9037fd13-b9bc-41ee-baab-b2c6a84bec55-0', usage_metadata={'input_tokens': 15, 'output_tokens': 20, 'total_tokens': 35})"
      ]
     },
     "execution_count": 6,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "message = model.invoke(prompt_value)\n",
    "message"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 8,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "'\\n\\nRobot: Why did the ice cream go to therapy? Because it was feeling a little rocky road.'"
      ]
     },
     "execution_count": 8,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "from langchain_openai import OpenAI\n",
    "\n",
    "llm = OpenAI(model=\"gpt-3.5-turbo-instruct\")\n",
    "llm.invoke(prompt_value)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 3. Output parser\n",
    "And lastly we pass our model output to the output_parser, which is a BaseOutputParser meaning it takes either a string or a BaseMessage as input. The specific StrOutputParser simply converts any input into a string.<br>\n",
    "最后，我们将 model 输出传递给 output_parser ，这是一个 BaseOutputParser 含义，它采用字符串或 a BaseMessage 作为输入。具体 StrOutputParser 只是将任何输入转换为字符串。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 9,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "'Why did the ice cream cone break up with the sundae? \\n\\nBecause it found someone cooler!'"
      ]
     },
     "execution_count": 9,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "output_parser.invoke(message)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 4. Entire Pipeline\n",
    "To follow the steps along:\n",
    "\n",
    "1. We pass in user input on the desired topic as {\"topic\": \"ice cream\"}\n",
    "2. The prompt component takes the user input, which is then used to construct a PromptValue after using the topic to construct the prompt.\n",
    "3. The model component takes the generated prompt, and passes into the OpenAI LLM model for evaluation. The generated output from the model is a ChatMessage object.\n",
    "4. Finally, the output_parser component takes in a ChatMessage, and transforms this into a Python string, which is returned from the invoke method.\n",
    "\n",
    "`Input: topic=ice cream` --Dict--> `PromptTemplate` --PromptValue--> `ChatModel` --ChatMessage--> `StrOutputParser` --String--> `Result`"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "input = {\"topic\": \"ice cream\"}\n",
    "\n",
    "prompt.invoke(input)\n",
    "# > ChatPromptValue(messages=[HumanMessage(content='tell me a short joke about ice cream')])\n",
    "\n",
    "(prompt | model).invoke(input)\n",
    "# > AIMessage(content=\"Why did the ice cream go to therapy?\\nBecause it had too many toppings and couldn't cone-trol itself!\")"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## RAG Search Example"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 11,
   "metadata": {},
   "outputs": [
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "d:\\Dev\\anaconda3\\envs\\langchain0_1\\Lib\\site-packages\\pydantic\\_migration.py:283: UserWarning: `pydantic.error_wrappers:ValidationError` has been moved to `pydantic:ValidationError`.\n",
      "  warnings.warn(f'`{import_path}` has been moved to `{new_location}`.')\n"
     ]
    },
    {
     "data": {
      "text/plain": [
       "'Harrison worked at Kensho.'"
      ]
     },
     "execution_count": 11,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "from langchain_openai import ChatOpenAI\n",
    "\n",
    "model = ChatOpenAI(model=\"gpt-4o-mini\")\n",
    "# Requires:\n",
    "# pip install langchain docarray tiktoken\n",
    "\n",
    "from langchain_community.vectorstores import DocArrayInMemorySearch\n",
    "from langchain_core.output_parsers import StrOutputParser\n",
    "from langchain_core.prompts import ChatPromptTemplate\n",
    "from langchain_core.runnables import RunnableParallel, RunnablePassthrough\n",
    "from langchain_openai import OpenAIEmbeddings\n",
    "\n",
    "vectorstore = DocArrayInMemorySearch.from_texts(\n",
    "    [\"harrison worked at kensho\", \"bears like to eat honey\"],\n",
    "    embedding=OpenAIEmbeddings(),\n",
    ")\n",
    "retriever = vectorstore.as_retriever()\n",
    "\n",
    "template = \"\"\"Answer the question based only on the following context:\n",
    "{context}\n",
    "\n",
    "Question: {question}\n",
    "\"\"\"\n",
    "prompt = ChatPromptTemplate.from_template(template)\n",
    "output_parser = StrOutputParser()\n",
    "\n",
    "setup_and_retrieval = RunnableParallel(\n",
    "    {\"context\": retriever, \"question\": RunnablePassthrough()} # 递用户的问题：\n",
    ")\n",
    "chain = setup_and_retrieval | prompt | model | output_parser\n",
    "\n",
    "chain.invoke(\"where did harrison work?\")"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "With the flow being:\n",
    "\n",
    "1. The first steps create a RunnableParallel object with two entries. The first entry, context will include the document results fetched by the retriever. The second entry, question will contain the user’s original question. To pass on the question, we use RunnablePassthrough to copy this entry.<br>\n",
    "第一步创建一个包含两个条目的 RunnableParallel 对象。第一个条目 context 将包括检索器获取的文档结果。第二个条目 question 将包含用户的原始问题。为了传递问题，我们用来 RunnablePassthrough 复制此条目。\n",
    "2. Feed the dictionary from the step above to the prompt component. It then takes the user input which is question as well as the retrieved document which is context to construct a prompt and output a PromptValue.<br>\n",
    "将字典从上述步骤馈送到 prompt 组件。然后，它接受用户输入 question 以及检索到的文档，该文档将 context 构造提示并输出 PromptValue。\n",
    "3. The model component takes the generated prompt, and passes into the OpenAI LLM model for evaluation. The generated output from the model is a ChatMessage object.<br>\n",
    "该 model 组件接受生成的提示，并传递到 OpenAI LLM 模型中进行评估。模型生成的输出是一个 ChatMessage 对象。\n",
    "4. Finally, the output_parser component takes in a ChatMessage, and transforms this into a Python string, which is returned from the invoke method.<br>\n",
    "最后，该 output_parser 组件接收 ChatMessage ，并将其转换为 Python 字符串，该字符串从 invoke 方法返回。\n",
    "\n",
    "\n",
    "\n",
    "\n",
    "\n",
    "`Question`-->`RunnableParallel`--Question-->\n",
    "\n",
    "* `RunnablePassThrough`--question=Question-->\n",
    "* `Retriever`--context=retrieved docs-->\n",
    "\n",
    "`PromptTemplate`--PromptValue-->`ChatModel`--ChatMessage-->`StrOutputParser`--String-->`Result`"
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "langchain0_1",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.11.9"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 2
}
